package com.ezlcp.commons.utils;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.ezlcp.commons.constant.Constants;
import com.ezlcp.commons.model.SysUser;
import com.ezlcp.commons.redis.RedisRepository;
import com.ezlcp.commons.tool.Encrypt;
import com.ezlcp.commons.tool.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author Elwin ZHANG
 * @description Token管理工具
 * @date 2023/2/1 17:54
 */
@Slf4j
public class TokenUtil {
    public static final int LOGIN_IDLE_MINUTES = 60;
    public static final int TOKEN_REFRESH_MINUTES = 5;
    public static final String TOKEN_PREFIX = "token:";
    public static final String MESSAGE_PREFIX = "message:";
    public static final String ONLINE_USER_KEY = "online:";

    /**
     * 获取Token
     */
    public static String getToken() {
        HttpServletRequest request = RequestUtil.getCurRequest();
        String token = request.getHeader(Constants.TOKEN);
        if (StringUtils.isNotEmpty(token)) {
            return token;
        }
        token = request.getHeader(Constants.Authorization);
        if (StringUtils.isNotEmpty(token)) {
            return token;
        }
        token = request.getParameter(Constants.TOKEN);
        if (StringUtils.isNotEmpty(token)) {
            return token;
        }
        return "";
    }

    /**
     * 获取当前登录的用户信息
     *
     * @return
     */
    @SneakyThrows
    public static SysUser getLoginUser() {
        String accessToken = getToken();
        if (StringUtils.isNotEmpty(accessToken)) {
            return getUserByToken(accessToken);
        }
        return null;
    }

    /***
     * @description: 根据Token获取缓存的用户信息, 并更新缓存时间
     * @param token 客户端返回的token
     * @author Elwin ZHANG
     * @date 2022/5/6 17:32
     */
    public static SysUser getUserByToken(String token) {
        Object result = getValueByToken(token);
        if (result == null) {
            return null;
        }
        try {
            SysUser user = JSON.parseObject(result.toString(), SysUser.class);
            refreshToken(user);
            return user;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /***
     * @description: 根据key为token值查询redis中对应value值
     * @param token 传入token
     * @return java.lang.Object
     * @author Weixuan LONG
     * @date 2022/6/16 9:46
     */
    public static Object getValueByToken(String token) {
        if (!token.contains(":")) {
            token = TOKEN_PREFIX + token;
        }
        //获取用户信息
        return getRedisRepository().get(token);
    }

    /***
     * @description 获取Spring上下文中的redisRepository对象
     * @return com.ezlcp.commons.redis.RedisRepository
     * @author Elwin ZHANG
     * @date 2023/2/2 13:36
     */
    public static RedisRepository getRedisRepository() {
        return SpringUtil.getBean("redisRepository", RedisRepository.class);
    }

    /***
     * @description: 根据用户ID生成token并缓存到redis
     * @param sysUser  系统用户
     * @return java.lang.String
     * @author Elwin ZHANG
     * @date 2022/5/6 16:25
     */
    public static String newTokenAndCache(SysUser sysUser) {
        if (sysUser == null) {
            return null;
        }
        String ip = RequestUtil.getIpAddress();
        long timeMillis = System.currentTimeMillis();
        String tokenSource = sysUser.getUserId() + ":" + sysUser.getClientType() + "@" + ip;
        log.debug("==========tokenSource:" + tokenSource);
        String token = Encrypt.getFirstPassword(tokenSource);
        checkCacheSeconds(sysUser);
        sysUser.setTimeMillis(timeMillis);
        String key = TOKEN_PREFIX + token;
        getRedisRepository().setExpire(key, JSON.toJSONString(sysUser), sysUser.getCacheSeconds() * 60);
        return token;
    }

    /***
     * @description: 检查token保存时长值，如不存在赋值默认值
     * @author Elwin ZHANG
     * @date 2022/5/24 10:41
     */
    private static void checkCacheSeconds(SysUser sysUser) {
        long cacheSeconds = sysUser.getCacheSeconds();
        //如果已经有缓存，则退出
        if (cacheSeconds > 0) {
            return;
        }
        sysUser.setCacheSeconds(LOGIN_IDLE_MINUTES);
    }

    /***
     * @description: 为防止频繁刷新，N分钟更新一次token的过期时间
     * @param user 用户信息
     * @author Elwin ZHANG
     * @date 2022/5/24 11:34
     */
    private static void refreshToken(SysUser user) {
        long timeLen = System.currentTimeMillis() - user.getTimeMillis();
        if (timeLen <= 0) {
            return;
        }
        long seconds = timeLen / 1000 / 60;
        if (seconds < TOKEN_REFRESH_MINUTES) {
            return;
        }
        // 刷新token
        newTokenAndCache(user);
    }

    /***
     * @description: 移除缓存
     * @param token token值
     * @author Elwin ZHANG
     * @date 2022/5/24 11:33
     */
    public static void removeToken(String token) {
        String key = TOKEN_PREFIX + token;
        getRedisRepository().del(key);
    }

    /***
     * @description: 移除message缓存
     * @param token token值
     * @author Weixuan LONG
     * @date 2022/6/14 15:41
     */
    public static void removeMessage(String token) {
        String key = MESSAGE_PREFIX + token;
        getRedisRepository().del(key);
    }

    /***
     * @description 获取当前租户的在线用户tokens
     * @param tenantId 租房ID
     * @author Elwin ZHANG
     * @date 2023/2/7 15:53
     */
    public static List<SysUser> getOnlineUsers(String tenantId) {
        List<SysUser> list = new ArrayList<>();
        String key = ONLINE_USER_KEY + dealTenantId(tenantId);
        Object value = getRedisRepository().get(key);
        if (value == null) {
            return list;
        }
        String[] tokens = value.toString().split(",");
        for (var token : tokens) {
            var user = getUserByToken(token);
            if (user != null) {
                list.add(user);
            }
        }
        return list;
    }

    /***
     * @description 如果租户ID为空，作为key时改为0
     * @param tenantId 租户ID
     * @author Elwin ZHANG
     * @date 2023/2/7 17:34
     */
    public static String dealTenantId(String tenantId) {
        if (StringUtils.isEmpty(tenantId)) {
            return "0";
        }
        return tenantId;
    }

    /***
     * @description 缓存在线用户列表
     * @param tenantId 租户ID
     * @param tokens 用户Token列表（逗号分隔）
     * @author Elwin ZHANG
     * @date 2023/2/7 10:34
     */
    public static void setOnlineUsers(String tenantId, String tokens) {
        String key = ONLINE_USER_KEY + dealTenantId(tenantId);
        getRedisRepository().setExpire(key, tokens, 60 * 30);
    }

    /***
     * @description: 判断当前用户是否已存token
     * @param sysUser 登录用户信息
     * @return java.lang.String
     * @author Weixuan LONG
     * @date 2022/6/14 15:41
     */
    public static String dealOtherIpLogin(SysUser sysUser) {
        String otherToken = getOtherIpToken(sysUser);
        if (StringUtils.isEmpty(otherToken)) {
            return newTokenAndCache(sysUser);
        }
        // 若用户ID一致且ip不相同则新建强制下线message且新建token
        String message = genForceOfflineMessage("Your account login another IP: " + sysUser.getIpAddr());
        forcedOffline(otherToken, message);
        removeToken(otherToken);
        return newTokenAndCache(sysUser);
    }

    /***
    * @description  强制下载消息生成
    * @param message 原始消息内容
    * @return java.lang.String
    * @author Elwin ZHANG
    * @date 2023/4/14 17:33
    */
    public static String genForceOfflineMessage(String message){
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String now = format.format(new Date());
        return  now + "，" + message;
    }

    /***
     * @description: 尝试获取当前用户在其他IP登录的token
     * @param sysUser 用户
     * @return java.lang.String
     * @author Elwin ZHANG
     * @date 2022/6/16 16:25
     */
    public static String getOtherIpToken(SysUser sysUser) {
        Map<String, Object> map = getRedisRepository().getKeysValues("token");
        if (map == null) {
            return "";
        }
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            SysUser user = JSON.parseObject(entry.getValue().toString(), SysUser.class);
            String userId = user.getUserId();
            //不同用户跳过
            if (!sysUser.getUserId().equals(userId)) {
                continue;
            }
            //用户ID相同但，客户端类型不同也跳过。允许同时登录手机端和PC端
            if (sysUser.getClientType() != user.getClientType()) {
                continue;
            }
            //用户客户端相同，但IP不同则返回
            if (!sysUser.getIpAddr().equals(user.getIpAddr())) {
                return entry.getKey().split(":")[1];
            }
        }
        return "";
    }

    /***
     * @description: 强制下线，并redis存入message通知被下线用户
     * @param token 传入被下线token
     * @param message 提示语
     * @author Weixuan LONG
     * @date 2022/6/16 9:31
     */
    public static void forcedOffline(String token, String message) {
        String key = MESSAGE_PREFIX + token;
        getRedisRepository().setExpire(key, message, LOGIN_IDLE_MINUTES * 60);
    }

    /***
     * @description: 根据Token获取message缓存信息
     * @param token 客户端返回的token
     * @return java.lang.String
     * @author Weixuan LONG
     * @date 2022/6/14 15:52
     */
    public static String getMessageByToken(String token) {
        String key = MESSAGE_PREFIX + token;
        Object result = getRedisRepository().get(key);
        return ObjectUtil.isNull(result) ? null : result.toString();
    }

    /***
    * @description  获得去掉前缀的token，如果输入参数包含前缀的话
    * @param token token字符串
    * @return java.lang.String
    * @author Elwin ZHANG
    * @date 2023/4/17 11:02
    */
    public  static String getTokenWithNoPrefix(String token){
        if(StringUtils.isEmpty(token)){
            return "";
        }
        if(token.startsWith(TOKEN_PREFIX)){
            return token.replace(TOKEN_PREFIX,"");
        }
        return token;
    }

}


